summaryrefslogtreecommitdiff
path: root/app/api/data-room/[projectId]/folders/route.ts
diff options
context:
space:
mode:
Diffstat (limited to 'app/api/data-room/[projectId]/folders/route.ts')
-rw-r--r--app/api/data-room/[projectId]/folders/route.ts282
1 files changed, 282 insertions, 0 deletions
diff --git a/app/api/data-room/[projectId]/folders/route.ts b/app/api/data-room/[projectId]/folders/route.ts
new file mode 100644
index 00000000..0ddf48f5
--- /dev/null
+++ b/app/api/data-room/[projectId]/folders/route.ts
@@ -0,0 +1,282 @@
+// app/api/data-room/[projectId]/folders/route.ts
+import { NextRequest, NextResponse } from 'next/server';
+import { getServerSession } from 'next-auth/next';
+import { authOptions } from '@/app/api/auth/[...nextauth]/route';
+import { fileItems, fileActivityLogs } from '@/db/schema';
+import { and, eq } from 'drizzle-orm';
+import db from '@/db/db';
+
+// 폴더 생성
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ projectId: string }> }
+) {
+ try {
+ const { projectId } = await params;
+
+ // 세션 확인
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
+ }
+
+ // 내부 사용자만 폴더 생성 가능
+ if (session.user.domain === 'partners') {
+ return NextResponse.json({ error: 'Permission denied' }, { status: 403 });
+ }
+
+ // 요청 본문 파싱
+ const body = await request.json();
+ const { name, parentId, category } = body;
+
+ // 필수 필드 검증
+ if (!name || typeof name !== 'string' || !name.trim()) {
+ return NextResponse.json({ error: 'Folder name is required' }, { status: 400 });
+ }
+
+ // 폴더 이름 정리
+ const folderName = name.trim();
+
+ // 폴더 이름 유효성 검사
+ const invalidChars = /[<>:"|?*]/;
+ if (invalidChars.test(folderName)) {
+ return NextResponse.json({
+ error: 'Folder name contains invalid characters'
+ }, { status: 400 });
+ }
+
+ // 부모 폴더 정보 조회 및 경로 설정
+ let parentFolder = null;
+ let path = '/';
+ let depth = 0;
+ let inheritedCategory = category || 'confidential'; // 기본값을 스키마에 맞게 변경
+
+ if (parentId && parentId !== null) {
+ const parentFolderResult = await db
+ .select()
+ .from(fileItems)
+ .where(
+ and(
+ eq(fileItems.id, parentId),
+ eq(fileItems.projectId, projectId),
+ eq(fileItems.type, 'folder')
+ )
+ )
+ .limit(1);
+
+ if (parentFolderResult.length === 0) {
+ return NextResponse.json({ error: 'Parent folder not found' }, { status: 404 });
+ }
+
+ parentFolder = parentFolderResult[0];
+ // 경로 계산 (부모 경로 + 부모 이름 + /)
+ path = parentFolder.path + parentFolder.name + '/';
+ // depth는 부모 depth + 1
+ depth = (parentFolder.depth || 0) + 1;
+
+ // 카테고리가 지정되지 않았으면 부모 폴더의 카테고리 상속
+ if (!category) {
+ inheritedCategory = parentFolder.category || 'confidential';
+ }
+ }
+
+ // 같은 위치에 중복 이름 확인
+ const existingFolder = await db
+ .select()
+ .from(fileItems)
+ .where(
+ and(
+ eq(fileItems.projectId, projectId),
+ eq(fileItems.parentId, parentId || null),
+ eq(fileItems.name, folderName),
+ eq(fileItems.type, 'folder')
+ )
+ )
+ .limit(1);
+
+ if (existingFolder.length > 0) {
+ return NextResponse.json({
+ error: 'A folder with this name already exists'
+ }, { status: 409 });
+ }
+
+ // 새 폴더 생성 (UUID는 자동 생성됨)
+ const newFolder = {
+ projectId,
+ parentId: parentId || null,
+ name: folderName,
+ type: 'folder' as const,
+ path,
+ depth,
+ category: inheritedCategory,
+ size: 0,
+ mimeType: 'folder',
+ // filePath와 fileUrl은 폴더에서는 null
+ filePath: null,
+ fileUrl: null,
+ // 권한 설정
+ externalAccessLevel: 'view_only' as const,
+ externalAccessExpiry: null,
+ downloadCount: 0,
+ viewCount: 0,
+ // 메타데이터
+ metadata: {},
+ tags: null,
+ // 버전 관리
+ version: 1,
+ previousVersionId: null,
+ // 감사 로그
+ createdBy: Number(session.user.id),
+ updatedBy: Number(session.user.id),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+
+ // DB에 폴더 저장
+ const [insertedFolder] = await db
+ .insert(fileItems)
+ .values(newFolder)
+ .returning();
+
+ // 활동 로그 기록 (스키마가 있다면)
+ if (fileActivityLogs) {
+ try {
+ await db.insert(fileActivityLogs).values({
+ fileItemId: insertedFolder.id,
+ projectId,
+ action: 'create_folder',
+ actionDetails: {
+ folderName: folderName,
+ parentId: parentId || null,
+ parentName: parentFolder?.name || null,
+ path,
+ category: inheritedCategory,
+ depth,
+ },
+ userId: Number(session.user.id),
+ userEmail: session.user.email,
+ userDomain: session.user.domain || 'default',
+ ipAddress: request.ip || request.headers.get('x-forwarded-for') || null,
+ userAgent: request.headers.get('user-agent') || null,
+ createdAt: new Date(),
+ });
+ } catch (logError) {
+ // 로그 실패는 무시하고 계속 진행
+ console.error('Failed to create activity log:', logError);
+ }
+ }
+
+ console.log('Folder created successfully:', {
+ id: insertedFolder.id,
+ name: folderName,
+ path,
+ depth,
+ parentId: parentId || null,
+ });
+
+ // 성공 응답
+ return NextResponse.json({
+ success: true,
+ folder: insertedFolder,
+ message: 'Folder created successfully',
+ }, { status: 201 });
+
+ } catch (error) {
+ console.error('Folder creation error:', error);
+
+ // 에러 타입에 따른 응답
+ if (error instanceof Error) {
+ // PostgreSQL unique constraint violation
+ if (error.message.includes('unique') || error.message.includes('duplicate')) {
+ return NextResponse.json(
+ { error: 'A folder with this path already exists' },
+ { status: 409 }
+ );
+ }
+
+ // Foreign key constraint violation
+ if (error.message.includes('foreign key')) {
+ return NextResponse.json(
+ { error: 'Invalid project or parent folder' },
+ { status: 400 }
+ );
+ }
+
+ // 데이터베이스 연결 오류
+ if (error.message.includes('connect')) {
+ return NextResponse.json(
+ { error: 'Database connection failed' },
+ { status: 503 }
+ );
+ }
+ }
+
+ // 일반 오류
+ return NextResponse.json(
+ {
+ error: 'Failed to create folder',
+ details: process.env.NODE_ENV === 'development' ? error?.message : undefined
+ },
+ { status: 500 }
+ );
+ }
+}
+
+// 폴더 목록 조회
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ projectId: string }> }
+) {
+ try {
+ const { projectId } = await params;
+
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
+ }
+
+ // URL 파라미터에서 parentId 가져오기
+ const { searchParams } = new URL(request.url);
+ const parentId = searchParams.get('parentId');
+
+ // 폴더만 조회
+ const conditions = [
+ eq(fileItems.projectId, projectId),
+ eq(fileItems.type, 'folder'),
+ ];
+
+ if (parentId) {
+ conditions.push(eq(fileItems.parentId, parentId));
+ } else {
+ conditions.push(eq(fileItems.parentId, null));
+ }
+
+ const folders = await db
+ .select()
+ .from(fileItems)
+ .where(and(...conditions))
+ .orderBy(fileItems.name);
+
+ // 파트너사 사용자의 경우 접근 가능한 폴더만 필터링
+ let filteredFolders = folders;
+ if (session.user.domain === 'partners') {
+ // category가 'confidential'이 아닌 폴더만 표시
+ filteredFolders = folders.filter(folder =>
+ folder.category !== 'confidential' ||
+ folder.externalAccessLevel !== null
+ );
+ }
+
+ return NextResponse.json({
+ folders: filteredFolders,
+ count: filteredFolders.length,
+ });
+
+ } catch (error) {
+ console.error('Folder list error:', error);
+ return NextResponse.json(
+ { error: 'Failed to load folders' },
+ { status: 500 }
+ );
+ }
+} \ No newline at end of file